from fastapi import APIRouter, Request, HTTPException
import hmac
import hashlib
import logging
import urllib.parse
import os
import time
import secrets
import requests
import traceback
from typing import Optional, Dict, Any, List
from app.conf.config import config, logger, Config

# 创建配置实例
# 已经从app.conf.config导入了config实例，无需再次创建
from app.services.shopify_api_service import ShopifyApiService
from app.utils.db_utils import MySQLHelper

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/oauth", tags=["shopify-oauth"])

# 从配置文件获取Shopify API凭证，环境变量优先
SHOPIFY_API_KEY = os.getenv("SHOPIFY_API_KEY", getattr(config, "SHOPIFY_API_KEY", ""))
SHOPIFY_API_SECRET = os.getenv("SHOPIFY_API_SECRET", getattr(config, "SHOPIFY_API_SECRET", ""))

# 验证API凭证是否配置
if not SHOPIFY_API_KEY or SHOPIFY_API_KEY == "YOUR_SHOPIFY_API_KEY":
    logger.warning("Shopify API Key未正确配置，可能会导致OAuth流程失败")
if not SHOPIFY_API_SECRET or SHOPIFY_API_SECRET == "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":
    logger.warning("Shopify API Secret未正确配置，可能会导致OAuth流程失败")

# 存储生成的state参数，用于回退方案
STATE_STORE = {}

# 数据库操作函数将用于存储state参数，确保可靠性和分布式部署支持

# 时间戳最大允许时间差（秒） - 增加到15分钟以处理网络延迟和时钟差异
MAX_TIMESTAMP_DIFF = 900  # 15分钟

@router.get("/shopify/callback")
async def shopify_oauth_callback(
    request: Request,
    code: str,
    shop: str,
    hmac: str,
    timestamp: str,
    state: Optional[str] = None,
    host: Optional[str] = None,
    locale: Optional[str] = None,
    session: Optional[str] = None
):
    """
    Shopify OAuth回调接口 - 严格按照Shopify OAuth认证流程的第二步实现
    
    Args:
        request: FastAPI请求对象
        code: Shopify授权码，用于获取访问令牌
        shop: Shopify店铺域名
        hmac: 请求参数的HMAC签名，用于验证请求的真实性
        timestamp: 请求的时间戳
        state: 状态参数，用于防止CSRF攻击
        locale: 可选的语言区域参数
        session: 可选的会话参数
    
    Returns:
        RedirectResponse: 授权成功后重定向到应用主页
    """
    try:
        # 1. 验证店铺域名格式 - 严格验证
        if not shop:
            logger.warning(f"缺少店铺域名参数")
            raise HTTPException(status_code=400, detail="缺少店铺域名参数")
        
        # 规范化店铺域名格式
        if not shop.endswith('.myshopify.com'):
            shop = f"{shop}.myshopify.com"
        
        # 记录日志，但不记录敏感信息
        logger.info(f"接收到Shopify回调，店铺: {shop}")
        logger.debug(
            "OAuth callback params received",
            extra={
                "shop": shop,
                "has_state": state is not None,
                "has_host": host is not None,
                "timestamp": timestamp,
            },
        )

        # 2. 验证时间戳，防止重放攻击（官方推荐，允许一定时间窗口）
        if not verify_timestamp(timestamp):
            logger.warning("OAuth callback timestamp validation failed", extra={"shop": shop, "timestamp": timestamp})
            raise HTTPException(status_code=401, detail="timestamp validation failed")

        # 3. 验证state参数，防止CSRF攻击
        if not state:
            logger.warning("Missing state parameter in OAuth callback", extra={"shop": shop})
            raise HTTPException(status_code=401, detail="missing state parameter")

        if not verify_state_parameter(state, shop):
            logger.warning("State validation failed", extra={"shop": shop})
            raise HTTPException(status_code=401, detail="invalid state parameter")

        # 4. 验证HMAC签名，确保请求来自Shopify
        query_items = list(request.query_params.multi_items())
        is_valid = verify_shopify_hmac(
            query_items=query_items,
            shared_secret=SHOPIFY_API_SECRET,
            provided_hmac=hmac
        )

        if is_valid:
            logger.info(f"HMAC验证成功: {shop}")
            
            # 5. 获取access_token
            token_data = get_shopify_access_token(shop, code)
            if not token_data or "access_token" not in token_data:
                raise HTTPException(status_code=500, detail="failed to exchange authorization code")

            access_token = token_data["access_token"]
            granted_scope = token_data.get("scope")
            
            # 6. 获取店铺信息
            shopify_api = ShopifyApiService()
            shop_info = shopify_api.get_complete_shop_info(shop, access_token)
            
            # 7. 保存用户信息到数据库
            save_user_info(shop, access_token, granted_scope, shop_info)
            
            # 8. 将已使用的state参数标记为已使用
            if state:
                delete_state_from_db(shop, state)
                # 同时从内存中清除
                if shop in STATE_STORE:
                    del STATE_STORE[shop]
            
            # 9. 重定向到应用主页或成功页面 - 符合标准OAuth流程
            # 可以从环境变量或配置中获取应用主页URL
            app_home_url = os.getenv("SHOPIFY_APP_HOME_URL", f"/api/v1/app?shop={shop}")
            from fastapi.responses import RedirectResponse
            return RedirectResponse(url=app_home_url)
        else:
            logger.warning(f"HMAC验证失败: {shop}")
            raise HTTPException(status_code=401, detail="无效的Shopify回调请求")
            
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"处理Shopify回调时发生错误: {shop}, 错误: {str(e)}")
        raise HTTPException(status_code=500, detail="处理回调时发生内部错误")

@router.get("/shopify/authorize")
async def shopify_authorize(
    request: Request,
    shop: str
):
    """
    Shopify应用授权入口 - 严格按照Shopify OAuth认证流程的第一步实现
    
    Args:
        request: FastAPI请求对象
        shop: Shopify店铺域名
    
    Returns:
        RedirectResponse: 重定向到Shopify授权页面
    """
    try:
        # 确保数据库表存在
        ensure_oauth_states_table_exists()
        
        # 验证店铺域名格式 - 严格验证，确保只接受合法的Shopify域名
        if not shop:
            logger.warning(f"缺少店铺域名参数")
            raise HTTPException(status_code=400, detail="缺少店铺域名参数")
            
        # 规范化店铺域名格式
        if not shop.endswith('.myshopify.com'):
            shop = f"{shop}.myshopify.com"
        
        # 生成安全的state参数 - 使用secrets模块确保密码学安全
        random_str = secrets.token_urlsafe(32)
        timestamp = str(int(time.time()))
        shop_hash = hashlib.sha256(shop.encode()).hexdigest()[:16]
        state = f"{random_str}_{timestamp}_{shop_hash}"
        
        # 保存state参数到数据库
        if not save_state_to_db(shop, state):
            logger.error(f"保存state参数失败，授权流程中止: {shop}")
            raise HTTPException(status_code=500, detail="初始化授权失败")
        
        # 构建授权URL - 严格按照Shopify OAuth 2.0规范
        redirect_uri = request.url_for("shopify_oauth_callback")._url
        
        # 确保redirect_uri是绝对URL并正确编码
        if not redirect_uri.startswith(('http://', 'https://')):
            # 构建完整URL
            scheme = request.headers.get('x-forwarded-proto', 'https')
            host = request.headers.get('host', '')
            path = request.url_for("shopify_oauth_callback").path
            redirect_uri = f"{scheme}://{host}{path}"
        
        # 设置请求的权限范围 - 从配置读取，确保与Partners可用范围一致
        try:
            scopes_cfg = getattr(config, "SHOPIFY_API_SCOPES", [])
            if isinstance(scopes_cfg, (list, tuple)):
                scope = ",".join(scopes_cfg)
            elif isinstance(scopes_cfg, str):
                scope = scopes_cfg
            else:
                scope = ""
        except Exception:
            scope = ""
        
        # 构建符合Shopify要求的授权URL
        authorize_url = (
            f"https://{shop}/admin/oauth/authorize"
            f"?client_id={SHOPIFY_API_KEY}"
            f"&scope={urllib.parse.quote_plus(scope)}"
            f"&redirect_uri={urllib.parse.quote_plus(redirect_uri)}"
            f"&state={urllib.parse.quote_plus(state)}"
        )
        
        logger.info(f"生成符合Shopify要求的授权URL: {shop}")
        
        # 直接重定向到Shopify授权页面，符合标准OAuth流程
        from fastapi.responses import RedirectResponse
        return RedirectResponse(url=authorize_url)
        
    except Exception as e:
        logger.error(f"生成授权URL时发生错误: {shop}, 错误: {str(e)}")
        raise HTTPException(status_code=500, detail="生成授权URL时发生内部错误")

def verify_timestamp(timestamp: str) -> bool:
    """
    验证时间戳是否在允许的时间范围内，防止重放攻击
    
    Args:
        timestamp: 时间戳字符串
    
    Returns:
        bool: 时间戳是否有效
    """
    try:
        # 记录接收到的时间戳以便调试
        logger.debug(f"验证时间戳: {timestamp}")
        
        current_time = int(time.time())
        
        # 尝试转换时间戳
        try:
            request_time = int(timestamp)
        except ValueError:
            logger.warning(f"无效的时间戳格式: {timestamp}")
            # 在开发/测试环境中可以临时返回True以绕过验证
            # return True  # 仅用于测试环境
            return False
        
        # 计算时间差
        time_diff = abs(current_time - request_time)
        logger.debug(f"当前时间: {current_time}, 请求时间: {request_time}, 时间差: {time_diff}秒")
        
        # 检查时间差是否在允许的范围内
        is_valid = time_diff <= MAX_TIMESTAMP_DIFF
        
        if not is_valid:
            logger.warning(f"时间戳过期: 时间差{time_diff}秒超过允许的{MAX_TIMESTAMP_DIFF}秒")
        
        return is_valid
    except Exception as e:
        logger.error(f"验证时间戳时发生错误: {str(e)}")
        # 在开发/测试环境中，可以临时返回True以绕过时间戳验证
        # return True  # 仅用于测试环境
        return False

def ensure_oauth_states_table_exists() -> bool:
    """
    确保shopify_oauth_states表存在，如果不存在则创建
    严格按照Shopify OAuth安全最佳实践设计表结构
    
    Returns:
        bool: 表是否已存在或创建成功
    """
    try:
        # 使用正确的数据库名称
        db_helper = MySQLHelper(database="shopify_app")
        
        # 创建表的SQL语句 - 符合Shopify OAuth安全最佳实践
        create_table_query = """
        CREATE TABLE IF NOT EXISTS shopify_oauth_states (
            `id` INT AUTO_INCREMENT COMMENT '状态ID，自增主键',
            `shop_domain` VARCHAR(255) NOT NULL COMMENT '店铺域名',
            `state` VARCHAR(255) NOT NULL COMMENT 'OAuth状态参数',
            `created_at` INT NOT NULL COMMENT '创建时间戳',
            `expires_at` INT NOT NULL COMMENT '过期时间戳',
            `is_used` TINYINT(1) DEFAULT 0 COMMENT '是否已使用: 0=未使用, 1=已使用',
            PRIMARY KEY (`id`),
            UNIQUE KEY `idx_state` (`state`),
            INDEX `idx_shop_domain` (`shop_domain`),
            INDEX `idx_expires_at` (`expires_at`),
            INDEX `idx_is_used` (`is_used`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Shopify OAuth状态表，用于防止CSRF攻击';
        """
        
        db_helper.execute_sql(create_table_query)
        logger.info("确保shopify_oauth_states表存在成功")
        return True
    except Exception as e:
        logger.error(f"确保shopify_oauth_states表存在失败: {str(e)}")
        return False

def save_state_to_db(shop: str, state: str) -> bool:
    """
    将state参数保存到数据库，符合Shopify OAuth安全最佳实践
    
    Args:
        shop: 店铺域名
        state: 生成的state参数
    
    Returns:
        bool: 是否保存成功
    """
    try:
        # 确保表存在
        ensure_oauth_states_table_exists()
        
        # 使用正确的数据库名称
        db_helper = MySQLHelper(database="shopify_app")
        timestamp = int(time.time())
        expires_at = timestamp + MAX_TIMESTAMP_DIFF
        
        # 使用INSERT而不是REPLACE，确保每个state都是唯一的
        query = """
        INSERT INTO shopify_oauth_states 
        (shop_domain, state, created_at, expires_at, is_used) 
        VALUES (%s, %s, %s, %s, 0)
        """
        
        params = (shop, state, timestamp, expires_at)
        db_helper.execute_sql(query, params)
        logger.info(f"成功保存state参数到数据库: {shop}")
        return True
    except Exception as e:
        logger.error(f"保存state参数到数据库失败: {shop}, 错误: {str(e)}")
        # 如果数据库操作失败，回退到内存存储（仅作为临时方案）
        if shop not in STATE_STORE:
            STATE_STORE[shop] = {}
        STATE_STORE[shop]["state"] = state
        STATE_STORE[shop]["timestamp"] = int(time.time())
        logger.warning(f"已回退到内存存储state参数: {shop}")
        return True

def verify_state_parameter(state: str, shop: str) -> bool:
    """
    验证state参数，防止CSRF攻击
    严格按照Shopify OAuth安全最佳实践实现
    
    Args:
        state: 接收到的state参数
        shop: 店铺域名
    
    Returns:
        bool: state参数是否有效
    """
    try:
        # 首先尝试从数据库验证
        try:
            # 使用正确的数据库名称
            db_helper = MySQLHelper(database="shopify_app")
            current_time = int(time.time())
            
            # 查询有效的state记录 - 必须同时匹配shop_domain、state，且未过期、未使用
            query = """
            SELECT id FROM shopify_oauth_states 
            WHERE shop_domain = %s AND state = %s AND expires_at >= %s AND is_used = 0
            """
            params = (shop, state, current_time)
            result = db_helper.execute_sql(query, params, fetchone=True)
            
            if result:
                logger.info(f"从数据库成功验证state参数: {shop}")
                return True
        except Exception as db_error:
            logger.error(f"数据库验证state参数失败: {shop}, 错误: {str(db_error)}")
            # 数据库验证失败，尝试内存存储作为回退
        
        # 回退到内存存储验证
        if shop in STATE_STORE:
            stored_state = STATE_STORE[shop].get("state")
            stored_time = STATE_STORE[shop].get("timestamp")
            
            # 检查state是否匹配以及是否过期
            current_time = int(time.time())
            is_valid_state = stored_state == state
            is_not_expired = (current_time - stored_time) <= MAX_TIMESTAMP_DIFF
            
            if is_valid_state and is_not_expired:
                logger.info(f"从内存成功验证state参数: {shop}")
                return True
        
        logger.warning(f"State参数验证失败: {shop}, 可能是CSRF攻击、state已过期或已被使用")
        return False
    except Exception as e:
        logger.error(f"验证state参数时发生错误: {shop}, 错误: {str(e)}")
        return False

def delete_state_from_db(shop: str, state: str) -> bool:
    """
    将已使用的state参数标记为已使用，而不是删除
    这样可以保留审计记录，符合Shopify安全最佳实践
    
    Args:
        shop: 店铺域名
        state: 使用过的state参数
    
    Returns:
        bool: 是否操作成功
    """
    try:
        # 使用正确的数据库名称
        db_helper = MySQLHelper(database="shopify_app")
        # 标记为已使用，而不是删除，保留审计记录
        query = "UPDATE shopify_oauth_states SET is_used = 1 WHERE shop_domain = %s AND state = %s"
        params = (shop, state)
        db_helper.execute_sql(query, params)
        logger.info(f"成功将state参数标记为已使用: {shop}")
        return True
    except Exception as e:
        logger.error(f"更新state参数状态失败: {shop}, 错误: {str(e)}")
        return False

def get_shopify_access_token(shop_domain: str, code: str) -> Optional[Dict[str, Any]]:
    """
    严格按照Shopify OAuth规范获取访问令牌
    参考文档: https://shopify.dev/docs/apps/auth/oauth
    
    Args:
        shop_domain: 已验证的Shopify店铺域名
        code: Shopify OAuth授权码
        
    Returns:
        str: access_token，如果获取失败返回None
    """
    try:
        # 验证参数
        if not shop_domain or not code:
            logger.error("获取访问令牌失败: 店铺域名或授权码为空")
            return None
        
        # 确保域名格式正确
        if not shop_domain.endswith('.myshopify.com'):
            shop_domain = f"{shop_domain}.myshopify.com"
        
        url = f"https://{shop_domain}/admin/oauth/access_token"
        payload = {
            "client_id": SHOPIFY_API_KEY,
            "client_secret": SHOPIFY_API_SECRET,
            "code": code
        }
        
        # 设置请求头
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
        
        logger.info(f"请求获取access_token: {shop_domain}")
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        
        # 使用raise_for_status自动处理HTTP错误
        response.raise_for_status()
        
        # 解析响应数据
        token_data = response.json()
        
        # 验证响应数据结构
        if 'access_token' not in token_data:
            logger.error(f"获取访问令牌失败: 响应中缺少access_token字段 - {token_data}")
            return None
        
        logger.info(f"成功获取access_token: {shop_domain}")
        return token_data
        
    except requests.RequestException as e:
        # 详细记录网络请求错误
        error_details = f"HTTP错误: {str(e)}"
        if hasattr(e, 'response') and e.response:
            error_details += f" - 响应体: {e.response.text[:500]}"  # 限制日志长度
        logger.error(f"获取access_token网络异常: {shop_domain}, {error_details}")
        return None
    except Exception as e:
        logger.error(f"获取access_token异常: {shop_domain}, 错误: {str(e)}")
        traceback.print_exc()
        return None

def save_user_info(shop_domain: str, access_token: str, scope: Optional[str] = None, shop_info: dict = None) -> bool:
    """
    安全地保存或更新Shopify店铺用户信息
    严格遵循数据安全最佳实践，确保敏感信息存储安全
    
    Args:
        shop_domain: 已验证的Shopify店铺域名
        access_token: 访问令牌
        shop_info: 店铺信息字典
    
    Returns:
        bool: 操作是否成功
    """
    try:
        # 参数验证
        if not shop_domain or not access_token:
            logger.error("保存用户信息失败: 店铺域名或访问令牌为空")
            return False
        
        db_helper = MySQLHelper(database="shopify_app")
        
        shop_data = shop_info or {}
        
        values = [
            shop_domain,
            access_token,
            scope,
            shop_data.get('shop_name') or shop_data.get('name'),
            shop_data.get('shop_owner'),
            shop_data.get('email'),
            shop_data.get('shop_country') or shop_data.get('country'),
            shop_data.get('shop_currency') or shop_data.get('currency'),
            shop_data.get('plan_name'),
            shop_data.get('plan_display_name'),
            shop_data.get('iana_timezone') or shop_data.get('timezone'),
        ]
        
        insert_sql = """
            INSERT INTO shopify_users (
                shop_domain,
                access_token,
                scope,
                shop_name,
                shop_owner,
                email,
                shop_country,
                shop_currency,
                shop_plan_name,
                shop_plan_display_name,
                shop_timezone,
                updated_at,
                last_login_at,
                last_activity_at,
                installed_at,
                uninstalled_at
            ) VALUES (
                %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
                NOW(), NOW(), NOW(), NOW(), NULL
            )
            ON DUPLICATE KEY UPDATE
                access_token = VALUES(access_token),
                scope = VALUES(scope),
                shop_name = VALUES(shop_name),
                shop_owner = VALUES(shop_owner),
                email = VALUES(email),
                shop_country = VALUES(shop_country),
                shop_currency = VALUES(shop_currency),
                shop_plan_name = VALUES(shop_plan_name),
                shop_plan_display_name = VALUES(shop_plan_display_name),
                shop_timezone = VALUES(shop_timezone),
                updated_at = NOW(),
                last_login_at = NOW(),
                last_activity_at = NOW(),
                uninstalled_at = NULL
        """
        
        db_helper.execute_sql(insert_sql, values)
        logger.info(f"保存或更新Shopify店铺信息成功: {shop_domain}")

        # 兼容性写入：尽量同步到可能存在的历史表结构，避免其他模块读取不到
        try:
            # 写入 users（最小字段集）
            legacy_upsert_users = """
                INSERT INTO users (shop_domain, access_token, updated_at)
                VALUES (%s, %s, NOW())
                ON DUPLICATE KEY UPDATE
                    access_token = VALUES(access_token),
                    updated_at = NOW()
            """
            db_helper.execute_sql(legacy_upsert_users, [shop_domain, access_token])
        except Exception:
            # 不影响主流程
            pass
        try:
            # 写入 shopify_app.users（最小字段集），兼容部分代码中使用的前缀引用
            legacy_upsert_ns_users = """
                INSERT INTO shopify_app.users (shop_domain, access_token, updated_at)
                VALUES (%s, %s, NOW())
                ON DUPLICATE KEY UPDATE
                    access_token = VALUES(access_token),
                    updated_at = NOW()
            """
            db_helper.execute_sql(legacy_upsert_ns_users, [shop_domain, access_token])
        except Exception:
            # 不影响主流程
            pass
        return True
            
    except Exception as e:
        logger.error(f"保存用户信息失败: {shop_domain}, 错误: {str(e)}")
        traceback.print_exc()
        return False

def verify_shopify_hmac(
    *,
    query_items: List[tuple],
    shared_secret: str,
    provided_hmac: str
) -> bool:
    """
    验证Shopify回调请求的HMAC签名 - 严格按照Shopify OAuth 2.0规范实现
    
    Args:
        code: 授权码
        shop: 店铺域名
        timestamp: 时间戳
        hmac: HMAC签名
        state: 状态参数
        api_secret: API密钥
    
    Returns:
        bool: 签名验证成功返回True，否则返回False
    """
    try:
        if not provided_hmac:
            logger.error("Shopify callback missing hmac parameter")
            return False

        filtered_params = [
            (key, value)
            for key, value in query_items
            if key not in ("hmac", "signature")
        ]

        # Shopify要求按照参数名字典序排序
        sorted_params = sorted(filtered_params, key=lambda item: item[0])

        # Shopify官方建议使用urlencode再次编码后生成待签名字符串
        encoded_message = urllib.parse.urlencode(sorted_params, doseq=True)

        digest = hmac.new(
            shared_secret.encode("utf-8"),
            encoded_message.encode("utf-8"),
            hashlib.sha256,
        ).hexdigest()

        return hmac.compare_digest(digest, provided_hmac)
    
    except Exception as e:
        logger.error(f"验证HMAC签名时发生错误: {str(e)}")
        # 需要导入traceback模块来使用format_exc
        import traceback
        logger.debug(f"错误详情: {traceback.format_exc()}")
        return False

# 在实际使用时，需要在main.py中注册这个路由
# 例如：app.include_router(shopify_oauth_route.router)